The following cell imports all the modules and functions required. Of note are lines 18 - 20 which import the custom electroninserts functions. These custom functions are located within electroninserts.py which can be perused to ensure it is running as you would expect.
In [1]:
import os
import dicom
from glob import glob
import zipfile
import time
import shutil
import datetime
import re
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
from IPython.core.display import display, Markdown
from bokeh.io import output_notebook, output_file, reset_output
from bokeh.plotting import show, save
from electroninserts import (
parameterise_single_insert, display_parameterisation,
convert2_ratio_perim_area, interactive, fallback_scatter)
print("All modules and functions successfully imported.")
In [2]:
# !pip install --upgrade version_information
# %load_ext version_information
# %version_information dicom, electroninserts, re, numpy, pandas, matplotlib, bokeh, version_information
Out[2]:
The following cells outline both your data itself the specific details of the data collection and definition. You can either write your own contents into the cells, or you can drag and drop the relevant files from your computer via windows explorer into the cell itself.
Once you have input your data to please consider uploading this data to http://simonbiggs.net/electrondata for the purpose of furthering this project.
In [3]:
# Archive old files to minimise confusion
old_data_files = glob('*.csv')
old_detail_files = glob('*.yaml')
old_report_files = glob('*.html')
old_zip_files = glob('*.zip')
old_files = np.concatenate([
old_data_files, old_detail_files, old_report_files, old_zip_files])
for data_file in old_files:
os.rename(data_file, "archive/{}".format(data_file))
In [4]:
%%writefile details.yaml
%YAML 1.1
---
Machine: Elekta Synergy Linac with Agility MLCs
R50 (cm):
6 MeV: 2.45
9 MeV: 3.47
12 MeV: 4.72
15 MeV: 5.8
18 MeV: 7.2
Applicator factors:
6 MeV:
6x6 cm: 0.871
10x10 cm: 1.000
14x14 cm: 1.029
20x20 cm: 1.022
25x25 cm: 1.026
9 MeV:
6x6 cm: 0.919
10x10 cm: 1.000
14x14 cm: 0.981
20x20 cm: 0.977
25x25 cm: 0.971
12 MeV:
6x6 cm: 0.971
10x10 cm: 1.000
14x14 cm: 0.990
20x20 cm: 0.985
25x25 cm: 0.983
15 MeV:
6x6 cm: 0.980
10x10 cm: 1.000
14x14 cm: 0.982
20x20 cm: 0.988
25x25 cm: 0.987
18 MeV:
6x6 cm: 1.011
10x10 cm: 1.000
14x14 cm: 0.983
20x20 cm: 0.979
25x25 cm: 0.967
SSD factors:
100 SSD: 1.000
Applicator sizes at isocentre:
6x6 cm: 6.3x6.3 cm
10x10 cm: 10.5x10.5 cm
14x14 cm: 14.7x14.7 cm
20x20 cm: 21.1x21.1 cm
25x25 cm: 26.3x26.3 cm
Definition of modelled factor: >
The factor given here is the insert factor defined as the portion of the
electron output factor that is dependent on the insert alone. The electron
output factor is defined as per AAPM TG 25 as the ratio of dose per
monitor unit at dmax.
Measurement details: >
Insert factors were measured in RW3 with an Advanced Markus on an Elekta
Agility linac. When the depth of maximum dose was shifted from the reference
depth this depth was searched for to a 1 mm resolution. All depth differences
took into account stopping power ratio corrections as per the protocol in
IAEA TRS 398.
Insert shielding material: Cerrobend
Software license of accompanying data: AGPLv3+
In [5]:
%%writefile data.csv
Label,Width (cm @ 100SSD),Length (cm @ 100SSD),Energy (MeV),Applicator (cm),SSD (cm),Insert factor (dose insert / dose open)
Simon 3cm circle,3.15,3.16,12,10,100,0.9294
Simon 3x5cm oval,3.16,5.25,12,10,100,0.9346
Simon 3x13cm oval,3.17,13.64,12,10,100,0.9533
Simon 3x6.5cm oval,3.17,6.83,12,10,100,0.9488
Simon 3x9cm oval,3.17,9.43,12,10,100,0.9488
P4,3.55,7.7,12,10,100,0.9443
Simon #57 cutout,3.66,5.04,12,10,100,0.9434
Simon #112 cutout,3.71,4.36,12,10,100,0.9488
Simon 4cm circle,4.2,4.21,12,10,100,0.956
Simon 4x10cm oval,4.21,10.51,12,10,100,0.9709
Simon 4x13cm oval,4.21,13.65,12,10,100,0.9756
Simon 4x6.5cm oval,4.21,6.82,12,10,100,0.9606
Simon 4x8cm oval,4.21,8.41,12,10,100,0.9709
Simon #20 cutout,4.38,5.47,12,10,100,0.9634
P10,4.48,7.29,12,10,100,0.9606
Simon #14 cutout,4.59,5.67,12,10,100,0.9588
Simon #3 cutout,4.59,6.54,12,10,100,0.9681
Simon #38 cutout,4.67,6.28,12,10,100,0.9737
Simon #22 cutout,5.21,11.4,12,10,100,0.9881
Simon 5cm circle,5.25,5.26,12,10,100,0.9709
Simon 5x10cm oval,5.26,10.52,12,10,100,0.9881
Simon 5x13cm oval,5.26,13.66,12,10,100,0.9872
Simon 5x8cm oval,5.26,8.41,12,10,100,0.9833
Simon #104 cutout,5.34,9.64,12,10,100,0.993
P40,5.43,11.02,12,10,100,0.9872
Simon #19 cutout,5.72,11.6,12,10,100,0.999
Simon #83 cutout,5.86,8.62,12,10,100,0.9891
Simon #58 cutout,6,7.98,12,10,100,0.9911
P3,6.04,9.22,12,10,100,0.999
Simon #33 cutout,6.08,6.64,12,10,100,0.993
Simon 6cm circle,6.3,6.33,12,10,100,0.9862
Simon #43 cutout,6.31,8.24,12,10,100,0.9921
Simon #82 cutout,6.41,8.69,12,10,100,0.999
Simon #16 cutout,6.53,10.99,12,10,100,1
Simon #109 cutout,6.54,8.41,12,10,100,0.993
Simon #106 cutout,6.64,9.81,12,10,100,0.999
Simon #34 cutout,6.78,10.98,12,10,100,1.007
P22,6.9,10.25,12,10,100,0.999
Simon #41 cutout,7.08,10.77,12,10,100,1.005
Simon #6 cutout,7.18,11.27,12,10,100,0.999
P38_1,7.21,9.03,12,10,100,1.0101
Simon 7cm circle,7.36,7.37,12,10,100,1.003
Simon #73 cutout,7.56,10.05,12,10,100,1.004
P12_1,7.6,10.26,12,10,100,1.0142
Simon #70 cutout,7.64,8.99,12,10,100,1.003
Simon #18 cutout,7.82,10.85,12,10,100,1.002
Simon #32 cutout,8.06,11.85,12,10,100,1.007
Simon 8cm circle,8.4,8.42,12,10,100,1.007
Simon 9cm circle,9.45,9.47,12,10,100,1.0081
Simon 5cm_15MeV,4.99,5,15,10,100,0.9747
P36_2,5.07,9.15,15,10,100,0.9911
Simon 5.3x12.5cm_15MeV,5.26,12.45,15,10,100,0.9921
Simon 6.1cm_15MeV,6.09,6.1,15,10,100,0.9794
Simon 6.7x12cm_15MeV,6.76,12,15,10,100,0.999
P5_2,6.97,9.79,15,10,100,0.996
Simon 7.25cm_15MeV,7.23,7.26,15,10,100,1.003
P28,7.4,9.25,15,10,100,1
P37_1,7.73,9.66,15,10,100,1.005
Simon 8.3cm_15MeV,8.28,8.3,15,10,100,1.005
Simon 8.5x10.9cm_15MeV,8.5,10.85,15,10,100,1.005
Simon 9.5cm_15MeV,9.49,9.5,15,10,100,1.0091
P31_1,4.9,9.4,18,10,100,0.9911
P36_1,4.9,9.4,18,10,100,0.994
Simon 5cm_18MeV,4.99,5,18,10,100,0.9823
Simon 5.3x12.5cm_18MeV,5.26,12.45,18,10,100,0.995
Simon 6.1cm_18MeV,6.09,6.1,18,10,100,0.9881
P37_4,6.67,9.28,18,10,100,1.0081
Simon 6.7x12cm_18MeV,6.76,12,18,10,100,0.999
P12_2,6.83,9.19,18,10,100,1.005
P13,7.1,8.79,18,10,100,0.997
Simon 7.25cm_18MeV,7.23,7.26,18,10,100,1.001
Simon 8.3cm_18MeV,8.28,8.3,18,10,100,1.003
Simon 8.5x10.9cm_18MeV,8.5,10.85,18,10,100,1.004
P42,9.14,11.32,18,10,100,0.998
Simon 9.5cm_18MeV,9.49,9.5,18,10,100,1.0081
P62,3.99,6.51,6,10,100,0.9643
P50,4.1,5.99,6,10,100,0.9662
Simon 5cm_6MeV,4.99,5,6,10,100,0.9766
Simon 5.3x12.5cm_6MeV,5.26,12.45,6,10,100,0.996
P7,5.69,7.58,6,10,100,0.995
Appears to be a standard 6cm P5_1,6,6,6,10,100,0.9887
P9_1,5.79,7.2,6,10,100,0.9852
Simon 6.1cm_6MeV,6.09,6.1,6,10,100,0.9901
P24,6.47,8.25,6,10,100,0.995
Simon 6.7x12cm_6MeV,6.76,12,6,10,100,0.998
Simon 7.25cm_6MeV,7.23,7.26,6,10,100,1.007
P35_2,7.43,10.3,6,10,100,0.994
Simon 8.3cm_6MeV,8.28,8.3,6,10,100,1.006
P6,8.5,10.73,6,10,100,0.999
Simon 8.5x10.9cm_6MeV,8.5,10.85,6,10,100,1.003
Simon 9.5cm_6MeV,9.49,9.5,6,10,100,1.005
P46,4.37,6.21,9,10,100,0.9606
Simon 5cm_9MeV,4.99,5,9,10,100,0.9588
P53,5.09,6.32,9,10,100,0.9756
Simon 5.3x12.5cm_9MeV,5.26,12.45,9,10,100,0.9901
P25,5.38,8.23,9,10,100,0.9852
P8,5.64,6.96,9,10,100,0.9775
Simon 6.1cm_9MeV,6.09,6.1,9,10,100,0.9823
P9_2,6.29,6.69,9,10,100,0.993
P34_3,6.72,10.23,9,10,100,0.997
Simon 6.7x12cm_9MeV,6.76,12,9,10,100,1
Simon 7.25cm_9MeV,7.23,7.26,9,10,100,1.005
Simon 8.3cm_9MeV,8.28,8.3,9,10,100,1.004
P34_2,8.37,10.6,9,10,100,0.997
Simon 8.5x10.9cm_9MeV,8.5,10.85,9,10,100,1.005
P17,8.6,9.81,9,10,100,1.0111
Simon 9.5cm_9MeV,9.49,9.5,9,10,100,1.007
P61,4.69,14.39,12,14,100,0.9901
P56,5.91,12.56,12,14,100,1.0091
P15_1,7.4,13.47,12,14,100,1.0101
P21,7.65,10.8,12,14,100,1.002
P45,9.11,12.34,12,14,100,1.0091
P19,7.08,8.84,15,14,100,1.0215
P15_2,8.57,12.91,15,14,100,1.0215
P27,9.25,11.42,15,14,100,1.0132
P37_3,7.3,12.97,18,14,100,1.0132
P38_2,8.69,11.49,18,14,100,1.0173
P33,6.21,10.43,6,14,100,0.999
P37_2,7.29,10.1,6,14,100,1.007
P20,8.6,9.24,6,14,100,1.006
P38_3,8.84,15.52,6,14,100,1.003
P16,9.05,11.99,6,14,100,1.0111
P35_1,9.28,10.58,6,14,100,1.0091
P23,9.87,13.16,6,14,100,1.005
P26,6.68,10.71,9,14,100,1.003
P14_2,7.03,12.47,9,14,100,1.0173
P34_1,7.66,11.25,9,14,100,1.006
P18,8.6,10.32,9,14,100,1.0101
P14_1,8.61,12.03,9,14,100,1.0163
P31_2,10.65,17.74,18,20,100,1.0142
P60,18.48,20.89,6,20,100,1.004
P41,7.85,24.61,9,20,100,1.0111
P32,9.3,22.7,9,20,100,1.0111
P51,10,28.65,15,25,100,1.0142
P49,2.69,4.21,6,6,100,0.8961
P43,2.8,2.9,6,6,100,0.885
P48,3.31,6.08,6,6,100,0.969
P47,3.39,3.4,6,6,100,0.9083
P35_3,3.48,4.78,6,6,100,0.939
Standard 4cm circle P63,4,4,6,6,100,0.9425
P59,4.1,5.89,6,6,100,0.9814
Standard 5cm circle P29,5,5,6,6,100,0.9891
P30,4.12,5.22,9,6,100,0.9597
P2,4.43,5.97,9,6,100,0.9728
P52,4.89,5.6,9,6,100,0.999
Appears to be a standard 6cm P58,6,6,9,6,100,1.002
P64,8.96,13.21,9,14,100,1.0121
P65 parameterised by hand,8.4,10.7,15,14,100,1.003
Now that you have created your details and data files these are to be timestamped for later reference. Please consider giving back to this project by uncommenting the final lines of this cell which will add these two newly created files to this public dropbox folder — http://simonbiggs.net/electrondata. To uncomment these lines, select them and press Ctrl + /.
In [6]:
# Create a timestamp for data and reports and rename the data file
timestamp = datetime.datetime.fromtimestamp(time.time()).strftime('%Y%m%d%H%M%S')
data_filename = "{}_data.csv".format(timestamp)
details_filename = "{}_details.yaml".format(timestamp)
shutil.move("data.csv", data_filename)
shutil.move("details.yaml", details_filename)
# # Uncomment these lines to give back to this project
# import dropbox
# simonbiggs_electroninserts_accesstoken = '5_VQ4CH7dO0AAAAAAAAhYhsClbUdrEnWKvMMnWagVXqKDTlTdf45ZwlKBp6Q2Rhq'
# dpx = dropbox.dropbox.Dropbox(simonbiggs_electroninserts_accesstoken)
# with open(data_filename, 'rb') as file:
# dpx.files_upload(file, "/{}".format(data_filename))
# with open(details_filename, 'rb') as file:
# dpx.files_upload(file, "/{}".format(details_filename))
Out[6]:
Once your csv has been created it can be loaded into a pandas DataFrame as such:
In [7]:
data = pd.read_csv(data_filename)
data
Out[7]:
Singling out a single energy, applicator, ssd is as simple as the following:
In [8]:
energy = 6
applicator = 10
ssd = 100
reference = (
(data['Energy (MeV)'] == energy) &
(data['Applicator (cm)'] == applicator) &
(data['SSD (cm)'] == ssd)
)
data[reference]
Out[8]:
It is also possible to test if a given combination has sufficient data:
In [9]:
number_of_data = len(data[reference])
print("Number of data = {}".format(number_of_data))
number_of_data >= 8
Out[9]:
In [10]:
reset_output()
output_notebook()
energy = 6
applicator = 10
ssd = 100
reference = (
(data['Energy (MeV)'] == energy) &
(data['Applicator (cm)'] == applicator) &
(data['SSD (cm)'] == ssd)
)
input_dataframe = data[reference]
label = np.array(input_dataframe['Label']).astype(str)
width_data = np.array(input_dataframe['Width (cm @ 100SSD)']).astype(float)
length_data = np.array(input_dataframe['Length (cm @ 100SSD)']).astype(float)
factor_data = np.array(input_dataframe['Insert factor (dose insert / dose open)']).astype(float)
ratio_perim_area_data = convert2_ratio_perim_area(width_data, length_data)
figure = interactive(
width_data, length_data, ratio_perim_area_data, factor_data, label)
show(figure)
Out[10]:
In [11]:
reset_output()
output_notebook()
# output_file("test.html", title="test")
energy = 9
applicator = 6
ssd = 100
reference = (
(data['Energy (MeV)'] == energy) &
(data['Applicator (cm)'] == applicator) &
(data['SSD (cm)'] == ssd)
)
input_dataframe = data[reference]
label = np.array(input_dataframe['Label']).astype(str)
width_data = np.array(input_dataframe['Width (cm @ 100SSD)']).astype(float)
length_data = np.array(input_dataframe['Length (cm @ 100SSD)']).astype(float)
factor_data = np.array(input_dataframe['Insert factor (dose insert / dose open)']).astype(float)
figure = fallback_scatter(width_data, length_data, factor_data, label)
show(figure)
Out[11]:
In [12]:
energy_array = np.unique(data['Energy (MeV)'])
applicator_array = np.unique(data['Applicator (cm)'])
ssd_array = np.unique(data['SSD (cm)'])
print("Iterate over the following scenarios:")
print("Energy = {}".format(energy_array))
print("Applicator = {}".format(applicator_array))
print("SSD = {}".format(ssd_array))
In [13]:
# reset_output()
# output_notebook()
for energy in energy_array:
for applicator in applicator_array:
for ssd in ssd_array:
reference = (
(data['Energy (MeV)'] == energy) &
(data['Applicator (cm)'] == applicator) &
(data['SSD (cm)'] == ssd)
)
input_dataframe = data[reference]
label = np.array(input_dataframe['Label']).astype(str)
width_data = np.array(
input_dataframe['Width (cm @ 100SSD)']).astype(float)
length_data = np.array(
input_dataframe['Length (cm @ 100SSD)']).astype(float)
factor_data = np.array(
input_dataframe['Insert factor (dose insert / dose open)']).astype(float)
number_of_data = len(input_dataframe)
filename = "{}_{}energy_{}applicator_{}ssd_{}data.html".format(
timestamp, str(energy).zfill(2), str(applicator).zfill(2),
str(ssd).zfill(3), str(number_of_data).zfill(2))
title = "{}MeV | {}App | {}SSD".format(
energy, applicator, ssd)
reset_output()
output_file(filename, title=title)
if number_of_data >= 8:
ratio_perim_area_data = convert2_ratio_perim_area(width_data, length_data)
figure = interactive(
width_data, length_data, ratio_perim_area_data, factor_data, label)
else:
figure = fallback_scatter(width_data, length_data, factor_data, label)
save(figure)
In [14]:
created_reports = glob("{}*.html".format(timestamp))
zip_filename = '{}_reports.zip'.format(timestamp)
with zipfile.ZipFile(zip_filename, 'w', zipfile.ZIP_DEFLATED) as zipf:
for file in created_reports:
zipf.write(file)
zipf.write(details_filename)
zipf.write(data_filename)
display(Markdown("[To download reports: `Right click me > Save link as...`]({}_reports.zip)".format(timestamp)))
Copyright © 2016 Simon Biggs
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along with this program. If not, see http://www.gnu.org/licenses/.